/**
 * Title :	jFamilyTree generator
 * Description: jFamilyTree generator
 * Version:	provider
 * Copyright (c) 2001-2008
 * Terms of use:see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 */
package jFamilyTree.generator.tree;

import java.awt.Component;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.TransferHandler;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;

import jFamilyTree.generator.Pair;
import jFamilyTree.model.MenuItem;
import jFamilyTree.model.TreeMutableNode;

/**
 * Transfer handler for drag and drop operations
 * 
 * @author Bernard Bou
 */
public class TreeTransferHandler extends TransferHandler
{
	private static final long serialVersionUID = 1L;

	// F L A V O R

	/**
	 * Data flavour
	 */
	private static DataFlavor theFlavor;

	static
	{
		try
		{
			theFlavor = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" + DefaultMutableTreeNode.class.getName() + "\""); //$NON-NLS-1$ //$NON-NLS-2$
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
	}

	// T R A N S F E R A B L E . O B J E C T

	/**
	 * Transferable data
	 */
	class TransferableNode implements Transferable
	{
		/**
		 * Node
		 */
		private DefaultMutableTreeNode theNode;

		/**
		 * Constructor
		 * 
		 * @param thisNode
		 *        node object to transfer
		 */
		public TransferableNode(Object thisNode)
		{
			theNode = (DefaultMutableTreeNode) thisNode;
		}

		// flavor
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.datatransfer.Transferable#getTransferDataFlavors()
		 */
		public DataFlavor[] getTransferDataFlavors()
		{
			DataFlavor[] theseFlavors = { theFlavor };
			return theseFlavors;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.datatransfer.Transferable#isDataFlavorSupported(java.awt.datatransfer.DataFlavor)
		 */
		public boolean isDataFlavorSupported(DataFlavor thisFlavor)
		{
			return thisFlavor.equals(theFlavor);
		}

		// data
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.datatransfer.Transferable#getTransferData(java.awt.datatransfer.DataFlavor)
		 */
		public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException
		{
			return theNode;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.TransferHandler#getSourceActions(javax.swing.JComponent)
	 */
	@Override
	public int getSourceActions(JComponent c)
	{
		return TransferHandler.MOVE;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.TransferHandler#createTransferable(javax.swing.JComponent)
	 */
	@Override
	protected Transferable createTransferable(JComponent thisSourceComponent)
	{
		JTree thisSourceTree = (JTree) thisSourceComponent;
		return new TransferableNode(thisSourceTree.getSelectionModel().getSelectionPath().getLastPathComponent());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.TransferHandler#canImport(javax.swing.TransferHandler.TransferSupport)
	 */
	@Override
	public boolean canImport(TransferSupport thisSupport)
	{
		Pair<TreePath, TreePath> theseNodes = getNodes(thisSupport);
		if (theseNodes != null)
			return testMove(theseNodes.theFirst, theseNodes.theSecond);
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.TransferHandler#importData(javax.swing.TransferHandler.TransferSupport)
	 */
	@Override
	public boolean importData(TransferSupport thisSupport)
	{
		Pair<TreePath, TreePath> theseNodes = getNodes(thisSupport);
		if (theseNodes != null)
		{
			if (thisSupport.isDrop())
				return move((Tree) thisSupport.getComponent(), theseNodes.theFirst, theseNodes.theSecond);
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.TransferHandler#exportDone(javax.swing.JComponent, java.awt.datatransfer.Transferable, int)
	 */
	@Override
	protected void exportDone(JComponent thisComponent, Transferable data, int action)
	{
	}

	// I M P L E M E N T

	/**
	 * Get pair of source tree and destination treepaths
	 * 
	 * @param thisSupport
	 *        transfer support
	 * @return source tree and destination treepaths
	 */
	private Pair<TreePath, TreePath> getNodes(TransferSupport thisSupport)
	{
		// source flavour
		if (!thisSupport.isDataFlavorSupported(theFlavor))
			return null;

		// destination tree
		Component thisComponent = thisSupport.getComponent();
		JTree thisDestinationTree = (JTree) thisComponent;

		// destination data
		DropLocation thisDropLocation = thisSupport.getDropLocation();
		TreePath thisDestinationPath = thisDestinationTree.getClosestPathForLocation(thisDropLocation.getDropPoint().x, thisDropLocation.getDropPoint().y);
		DefaultMutableTreeNode thisDestinationNode = (DefaultMutableTreeNode) thisDestinationPath.getLastPathComponent();
		Object thisDestinationObject = thisDestinationNode.getUserObject();

		// source data
		TreePath thisSourcePath = null;
		Object thisData = null;
		Transferable thisTransferable = thisSupport.getTransferable();
		try
		{
			thisData = thisTransferable.getTransferData(theFlavor);
		}
		catch (UnsupportedFlavorException thisException)
		{
			return null;
		}
		catch (IOException thisException)
		{
			return null;
		}
		DefaultMutableTreeNode thisNode = (DefaultMutableTreeNode) thisData;
		Object thisSourceObject = thisNode.getUserObject();
		thisSourcePath = new TreePath(thisNode.getPath());
		
		// type check
		if (thisSourceObject instanceof TreeMutableNode && thisDestinationObject instanceof TreeMutableNode)
			return new Pair<TreePath, TreePath>(thisSourcePath, thisDestinationPath);
		if (thisSourceObject instanceof MenuItemWrapper && thisDestinationObject instanceof MenuWrapper)
			return new Pair<TreePath, TreePath>(thisSourcePath, thisDestinationPath);

		return null;
	}

	/**
	 * Whether source node can drag and drop to destination node
	 * 
	 * @param thisSource
	 *        source node
	 * @param thisDestination
	 *        destination node
	 * @return true if source node can drag and drop to destination node
	 */
	private boolean testMove(TreePath thisSource, TreePath thisDestination)
	{
		if (thisDestination != null && thisSource != null)
		{
			DefaultMutableTreeNode thisDestinationNode = (DefaultMutableTreeNode) thisDestination.getLastPathComponent();
			DefaultMutableTreeNode thisSourceNode = (DefaultMutableTreeNode) thisSource.getLastPathComponent();
			Object thisDestinationObject = thisDestinationNode.getUserObject();
			Object thisSourceObject = thisSourceNode.getUserObject();
			if (thisDestinationObject instanceof TreeMutableNode && thisSourceObject instanceof TreeMutableNode)
				return !thisSource.isDescendant(thisDestination);
			else if (thisDestinationObject instanceof MenuWrapper && thisSourceObject instanceof MenuItemWrapper)
				return !thisSource.isDescendant(thisDestination);
		}
		return false;
	}

	/**
	 * Move source node as child of destination node
	 * 
	 * @param thisSource
	 *        source node
	 * @param thisDestination
	 *        destination node
	 * @return true if successful
	 */
	private boolean move(Tree thisTree, TreePath thisSource, TreePath thisDestination)
	{
		// tree
		DefaultMutableTreeNode thisParentTreeNode = (DefaultMutableTreeNode) thisDestination.getLastPathComponent();
		DefaultMutableTreeNode thisTreeNode = (DefaultMutableTreeNode) thisSource.getLastPathComponent();

		// tree remove
		thisTree.removeFromParent(thisTreeNode);

		// tree add
		thisTree.addToParent(thisParentTreeNode, thisTreeNode);

		// objects
		Object thisParentObject = thisParentTreeNode.getUserObject();
		Object thisObject = thisTreeNode.getUserObject();

		if (thisObject instanceof TreeMutableNode && thisParentObject instanceof TreeMutableNode)
		{
			TreeMutableNode thisParentNode = (TreeMutableNode) thisParentObject;
			TreeMutableNode thisNode = (TreeMutableNode) thisObject;

			// object remove
			thisNode.removeFromParent();

			// object add
			thisNode.addToParent(thisParentNode);
		}
		else
		{
			MenuWrapper thisParentNode = (MenuWrapper) thisParentObject;
			MenuItemWrapper thisNode = (MenuItemWrapper) thisObject;
			List<MenuItem> thisMenu = thisParentNode.theMenu;
			MenuItem thisMenuItem = thisNode.theMenuItem;
			thisMenu.remove(thisMenuItem);
			thisMenu.add(thisMenu.size(), thisMenuItem);
		}
		return true;
	}
}
